﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace QueueSimulator
{
    public enum EventType { Arrival1, Departure1, Arrival2, Departure2, }
    public delegate void EventLogHandler(List<Event> list, Queue q1, Queue q2);

    class Simulator
    {
        private double m_time;
        private List<Event> m_eventList;
        private Queue m_queue1, m_queue2;
        private Random m_random;

        public List<Event> EventLog { get { return m_eventList; } }

        public event EventLogHandler OnEventLogChanges;

        public static int Iterations { get; private set; }
        public static double TotalTime { get; private set; }

        public Simulator(Queue queue1, Queue queue2)
        {
            m_time = 0;
            m_eventList = new List<Event>();
            m_queue1 = queue1;
            m_queue2 = queue2;

            m_random = new Random();

            Iterations = 0;
        }

        public void Run()
        {
            Schedule(EventType.Arrival1, m_time + m_random.Next(m_queue1.ArrivalTimeMin, m_queue1.ArrivalTimeMax));

            while (m_eventList.Count(e => !e.Done) > 0)
            {
                var ev = GetNextEvent();

                //count timers
                m_queue1.Statistics[m_queue1.Size].Time = Math.Round(m_queue1.Statistics[m_queue1.Size].Time + ev.Time - m_time, 2);
                m_queue2.Statistics[m_queue2.Size].Time = Math.Round(m_queue2.Statistics[m_queue2.Size].Time + ev.Time - m_time, 2);

                //update time
                m_time = ev.Time;

                //update percentages
                foreach (var s in m_queue1.Statistics)
                {
                    s.Percentage = Math.Round((100 * s.Time) / m_time, 2);
                }

                foreach (var s in m_queue2.Statistics)
                {
                    s.Percentage = Math.Round((100 * s.Time) / m_time, 2);
                }

                //process event
                switch (ev.Type)
                {
                    case EventType.Arrival1: Arrival1();
                        break;
                    case EventType.Departure1: Departure1();
                        break;
                    case EventType.Arrival2: Arrival2();
                        break;
                    case EventType.Departure2: Departure2();
                        break;
                }

                ev.Done = true;
                
                Iterations++;
                TotalTime = m_time;

                OnEventLogChanges(m_eventList, m_queue1, m_queue2);
                Thread.Sleep(10);
            }
        }

        private Event GetNextEvent()
        {
            Event ret = null;
            double t = double.MaxValue;

            foreach (var e in m_eventList)
            {
                if (!e.Done && e.Time < t)
                {
                    ret = e;
                    t = e.Time;
                }
            }

            return ret;
        }

        private void Arrival1()
        {
            if (m_queue1.Size < m_queue1.Capacity)
            {
                m_queue1.Size++;

                if (m_queue1.Size <= m_queue1.Servers)
                {
                    if (m_random.Next() < (1 - m_queue2.ArrivalPercentage))
                    {
                        Schedule(EventType.Departure1, m_time + m_random.Next(m_queue1.ServiceTimeMin, m_queue1.ServiceTimeMax));
                    }
                    else
                    {
                        Schedule(EventType.Arrival2, m_time + m_random.Next(m_queue1.ServiceTimeMin, m_queue1.ServiceTimeMax));
                    }
                }
            }

            Schedule(EventType.Arrival1, m_time + m_random.Next(m_queue1.ArrivalTimeMin, m_queue1.ArrivalTimeMax));
        }

        private void Departure1()
        {
            m_queue1.Size--;

            if (m_queue1.Size >= m_queue1.Servers)
            {
                if (m_random.Next() < (1 - m_queue2.ArrivalPercentage))
                {
                    Schedule(EventType.Departure1, m_time + m_random.Next(m_queue1.ServiceTimeMin, m_queue1.ServiceTimeMax));
                }
                else
                {
                    Schedule(EventType.Arrival2, m_time + m_random.Next(m_queue1.ServiceTimeMin, m_queue1.ServiceTimeMax));
                }
            }
        }

        private void Arrival2()
        {
            Departure1();

            if (m_queue2.Size < m_queue2.Capacity)
            {
                m_queue2.Size++;

                if (m_queue2.Size <= m_queue2.Servers)
                {
                    Schedule(EventType.Departure2, m_time + m_random.Next(m_queue2.ServiceTimeMin, m_queue2.ServiceTimeMax));
                }
            }
        }

        private void Departure2()
        {
            m_queue2.Size--;

            if (m_queue2.Size >= m_queue2.Servers)
            {
                Schedule(EventType.Departure2, m_time + m_random.Next(m_queue2.ServiceTimeMin, m_queue2.ServiceTimeMax));
            }
        }

        private void Schedule(EventType eventType, double time)
        {
            m_eventList.Add(new Event(eventType, Math.Round(time, 2)));

            OnEventLogChanges(m_eventList, m_queue1, m_queue2);
        }
    }
}
